﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Runtime.InteropServices;
using SilkroadSecurityApi;

/* At the time of writing, these are some valid command lines. Update the version as needed.
 * 121.128.133.28 15779 296 18
 * 212.24.57.34 15779 23 40
 * shlogin1.srocn.com 15779 177 4
 * gwgt1.silkroadonline.co.kr 15779 758 2
 * gw.conduongtolua.com.vn 15779 81 23
 * 58.64.28.187 15779 21 38
 */

namespace ServerStats
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Security security = new Security();
                TransferBuffer recv_buffer = new TransferBuffer(4096, 0, 0);
                List<Packet> packets = new List<Packet>();
                Stopwatch ping_timer = new Stopwatch();
                bool do_ping = false;
                Stopwatch stats_timer = new Stopwatch();
                bool do_stats = false;
                Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                uint version = UInt32.Parse(args[2]);
                uint locale = UInt32.Parse(args[3]);

                // Blocking connect to the server first, exception thrown on error
                s.Connect(args[0], Int32.Parse(args[1]));

                // Now make it non-blocking + disable nagle's algo
                s.Blocking = false;
                s.NoDelay = true;

                while (!Console.KeyAvailable)
                {
                    SocketError err;

                    // Receive logic, try to receive as much as possible, then pass to the security object
                    recv_buffer.Size = s.Receive(recv_buffer.Buffer, 0, recv_buffer.Buffer.Length, SocketFlags.None, out err);
                    if (err != SocketError.Success)
                    {
                        if (err != SocketError.WouldBlock)
                        {
                            Console.WriteLine("Error: Receive returned error code {0}.", err);
                            break;
                        }
                    }
                    else
                    {
                        if (recv_buffer.Size > 0)
                        {
                            security.Recv(recv_buffer);
                        }
                        else
                        {
                            Console.WriteLine("Status: The connection has been closed.");
                            break;
                        }
                    }

                    // Obtain all queued packets and add them to our own queue to process later.
                    List<Packet> tmp_packets = security.TransferIncoming();
                    if(tmp_packets != null)
                    {
                        packets.AddRange(tmp_packets);
                    }

                    if (packets.Count > 0)
                    {
                        foreach (Packet packet in packets)
                        {
                            byte[] packet_bytes = packet.GetBytes();

                            // Debug
                            Console.WriteLine("[S->C][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            // Identify
                            if (packet.Opcode == 0x2001)
                            {
                                Packet response = new Packet(0x6100, true, false);
                                response.WriteUInt8(locale);
                                response.WriteAscii("SR_Client");
                                response.WriteUInt32(version);
                                security.Send(response);

                                do_ping = true;
                                ping_timer.Start();
                            }

                            // Version response
                            else if (packet.Opcode == 0xA100)
                            {
                                byte result = packet.ReadUInt8();
                                if (result == 1)
                                {
                                    if (locale == 18)
                                    {
                                        Packet response = new Packet(0x6107, true, false);
                                        security.Send(response);
                                    }
                                    else
                                    {
                                        do_stats = true;
                                        stats_timer.Start();

                                        Packet response = new Packet(0x6101, true);
                                        security.Send(response);
                                    }
                                }
                                else
                                {
                                    result = packet.ReadUInt8();
                                    if (result == 0x02) // Updates available
                                    {
                                        String ip = packet.ReadAscii();
                                        ushort port = packet.ReadUInt16();
                                        uint new_version = packet.ReadUInt32();

                                        Console.WriteLine("New Version Avaliable -- [{0}][{1}:{2}]", new_version, ip, port);

                                        byte new_file = packet.ReadUInt8();
                                        while (new_file == 1)
                                        {
                                            uint file_id = packet.ReadUInt32();
                                            String file_name = packet.ReadAscii();
                                            String file_path = packet.ReadAscii();
                                            uint file_size = packet.ReadUInt32();
                                            byte file_pk2 = packet.ReadUInt8();
                                            new_file = packet.ReadUInt8();

                                            Console.WriteLine("[{0}][{1}][{2}] {3} bytes {4}", file_id, file_name, file_path, file_size, file_pk2 == 1 ? "(pk2)" : "");
                                        }
                                    }
                                    else if (result == 0x04) // Server down
                                    {
                                        Console.WriteLine("The GatewayServer is closed.");
                                    }
                                    else if (result == 0x05) // Version too old
                                    {
                                        Console.WriteLine("The version is too old.");
                                    }
                                    else if (result == 0x01) // Version too new
                                    {
                                        Console.WriteLine("The version is too new.");
                                    }
                                    else
                                    {
                                        Console.WriteLine("Unknown response {0}.", result);
                                    }

                                    s.Disconnect(false);
                                }
                            }

                            // Location response
                            else if (packet.Opcode == 0xA107)
                            {
                                byte count = packet.ReadUInt8();
                                for (int x = 0; x < count; ++x)
                                {
                                    byte id = packet.ReadUInt8();
                                    string host = packet.ReadAscii();
                                    ushort port = packet.ReadUInt16();

                                    Console.WriteLine("[{0}] {1}:{2}", id, host, port);
                                }

                                Console.WriteLine("");

                                do_stats = true;
                                stats_timer.Start();

                                Packet response = new Packet(0x6101, true);
                                security.Send(response);
                            }

                            // Server stats
                            else if (packet.Opcode == 0xA101)
                            {
                                byte new_entry = packet.ReadUInt8();
                                while (new_entry == 1)
                                {
                                    byte id = packet.ReadUInt8();
                                    string name = packet.ReadAscii();
                                    new_entry = packet.ReadUInt8();

                                    Console.WriteLine("[{0}] {1}", id, name);
                                }

                                new_entry = packet.ReadUInt8();
                                while (new_entry == 1)
                                {
                                    ushort id = 0;
                                    float ratio = 0;
                                    string name = null;
                                    char country = '?';
                                    byte state = 0;
                                    ushort cur = 0;
                                    ushort max = 0;
                                    byte extra1 = 0;
                                    byte extra2 = 0;

                                    id = packet.ReadUInt16();

                                    if (locale == 18)
                                    {
                                        name = packet.ReadAscii();
                                    }
                                    else if (locale == 40)
                                    {
                                        name = packet.ReadAscii(1251);
                                    }
                                    else if (locale == 2)
                                    {
                                        name = packet.ReadAscii(949);
                                    }
                                    else if (locale == 4)
                                    {
                                        name = packet.ReadAscii(936);
                                    }
                                    else if (locale == 23)
                                    {
                                        name = packet.ReadAscii(936);
                                    }
                                    else if (locale == 38)
                                    {
                                        name = packet.ReadAscii();
                                    }

                                    if (locale == 18)
                                    {
                                        country = name[0];
                                        name = name.Remove(0, 1);
                                        ratio = packet.ReadSingle();
                                    }
                                    else
                                    {
                                        cur = packet.ReadUInt16();
                                        max = packet.ReadUInt16();
                                    }

                                    state = packet.ReadUInt8();

                                    if (locale == 4 || locale == 23)
                                    {
                                        extra1 = packet.ReadUInt8();
                                        if (extra1 == 1)
                                        {
                                            extra2 = packet.ReadUInt8();
                                        }
                                    }

                                    new_entry = packet.ReadUInt8();

                                    if (locale == 18)
                                    {
                                        Console.WriteLine("[{0}] [{4}] {1} ({2}% full) [{3}]", id, name, ratio * 100.0f, state == 1 ? "Open" : "Check", country == '0' ? "Korea" : "USA");
                                    }
                                    else
                                    {
                                        Console.WriteLine("[{0}] {1} {2} / {3} [{4}]", id, name, cur, max, state == 1 ? "Open" : "Check");
                                    }
                                }

                                Console.WriteLine("");
                            }
                        }
                        packets.Clear();
                    }

                    // Check to see if we have any packets to send
                    List<KeyValuePair<TransferBuffer, Packet>> tmp_buffers = security.TransferOutgoing();
                    if (tmp_buffers != null)
                    {
                        foreach (var kvp in tmp_buffers)
                        {
                            TransferBuffer buffer = kvp.Key;
                            Packet packet = kvp.Value;

                            err = SocketError.Success;

                            // Since TCP is a stream protocol, we have to support partial sends. To do this, we
                            // will just loop until we send all the data or an exception is generated.

                            while (buffer.Offset != buffer.Size)
                            {
                                int sent = s.Send(buffer.Buffer, buffer.Offset, buffer.Size - buffer.Offset, SocketFlags.None, out err);
                                if (err != SocketError.Success)
                                {
                                    if (err != SocketError.WouldBlock)
                                    {
                                        Console.WriteLine("Error: Send returned error code {0}.", err);
                                        break;
                                    }
                                }
                                buffer.Offset += sent;
                                Thread.Sleep(1);
                            }

                            // We need to check for an error to break out of the foreach loop
                            if (err != SocketError.Success)
                            {
                                break;
                            }

                            byte[] packet_bytes = packet.GetBytes();

                            // Debug (logical packet)
                            //Console.WriteLine("*** Logical ***");
                            Console.WriteLine("[C->S][{0:X4}][{1} bytes]{2}{3}{4}{5}{6}", packet.Opcode, packet_bytes.Length, packet.Encrypted ? "[Encrypted]" : "", packet.Massive ? "[Massive]" : "", Environment.NewLine, Utility.HexDump(packet_bytes), Environment.NewLine);

                            // Debug (physical packet)
                            //Console.WriteLine("*** Physical ***");
                            //Console.WriteLine("[C->S][{0} bytes]{1}{2}{3}", packet_bytes.Length, Environment.NewLine, Utility.HexDump(buffer.Buffer), Environment.NewLine);

                            // If we should be ping'ing, we can reset the ping timer 
                            if (do_ping)
                            {
                                ping_timer.Reset();
                                ping_timer.Start();
                            }
                        }

                        // We need to check for an error to break out of the main loop
                        if (err != SocketError.Success)
                        {
                            break;
                        }
                    }

                    // Send the ping packet every 5s there is no other send
                    if (do_ping)
                    {
                        if (ping_timer.ElapsedMilliseconds > 5000)
                        {
                            ping_timer.Reset();
                            ping_timer.Start();

                            Packet response = new Packet(0x2002);
                            security.Send(response);
                        }
                    }

                    // Request stats each 15s
                    if (do_stats)
                    {
                        if (stats_timer.ElapsedMilliseconds > 15000)
                        {
                            stats_timer.Reset();
                            stats_timer.Start();

                            Packet response = new Packet(0x6101, true);
                            security.Send(response);
                        }
                    }

                    // Prevent 100% cpu use since this is a simple example
                    Thread.Sleep(1);
                }

                s.Shutdown(SocketShutdown.Both);
                s.Close();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Exception: {0}", ex);
            }
        }
    }
}
